<template>
  <div class="upload-container">
    <el-card class="upload-card" shadow="hover">
      <!-- 拖放上传区域 -->
      <div
          class="drop-area"
          :class="{ 'drop-active': isDragging }"
          @dragover.prevent="isDragging = true"
          @dragleave.prevent="isDragging = false"
          @drop.prevent="handleDrop"
      >
        <!-- 传统文件选择 -->
        <input
            type="file"
            ref="fileInput"
            class="file-input"
            accept="video/*"
            @change="handleFileSelect"
        />

        <!-- 拖放提示 -->
        <div class="drop-content">
          <el-icon class="upload-icon"><UploadFilled /></el-icon>
          <h3 class="drop-title">拖放视频文件到此处上传</h3>
          <p class="drop-desc">或</p>
          <el-button
              type="primary"
              class="browse-btn"
              @click="$refs.fileInput.click()"
          >
            <el-icon><FolderOpen /></el-icon> 选择视频文件
          </el-button>
          <p class="format-tip">支持 MP4、MOV、AVI 等常见视频格式</p>
        </div>
      </div>

      <!-- 已选文件信息 -->
      <div v-if="selectedFile" class="file-info-card">
        <div class="file-header">
          <el-icon class="file-icon"><VideoCamera /></el-icon>
          <div class="file-meta">
            <p class="file-name">{{ fileInfo.name }}</p>
            <p class="file-size">文件大小: {{ formatSize(fileInfo.size) }}</p>
          </div>
          <el-button
              class="remove-btn"
              @click="resetUpload"
          >
            <el-icon color="#f56c6c"><Delete /></el-icon>
          </el-button>
        </div>
      </div>

      <!-- 控制按钮区域 -->
      <div class="control-buttons" v-if="selectedFile">
        <el-button
            type="primary"
            size="default"
            :disabled="uploading || uploadResult !==null"
            @click="startUpload"
            class="main-btn"
        >
          <el-icon v-if="!uploading"><IconPlay /></el-icon>
          <el-icon v-if="uploading" class="loading-icon"><Loading /></el-icon>
          {{ uploading ? '上传中' : '开始上传' }}
        </el-button>
        <el-button
            type="danger"
            size="default"
            :disabled="!uploading"
            @click="cancelUpload"
            class="cancel-btn"
        >
          <el-icon><IconStop /></el-icon> 取消上传
        </el-button>
      </div>

      <!-- 上传进度区域 -->
      <div
          class="progress-section"
          v-if="selectedFile && (uploading || progress > 0 || uploadResult)"
      >
        <!-- 进度条 -->
        <div class="progress-container">
          <div class="progress-label">
            <span>上传进度</span>
            <span class="progress-percent">{{ progress.toFixed(2) }}%</span>
          </div>
          <el-progress
              :percentage="progress"
              :stroke-width="6"
              :status="progressStatus"
              class="progress-bar"
          ></el-progress>
        </div>

        <!-- 进度信息 -->
        <div class="progress-info">
          <div class="info-item">
            <el-icon color="#409EFF"><Clock /></el-icon>
            <span>{{ uploadStatus }}</span>
          </div>
          <div class="info-item">
            <el-icon color="#67C23A"><Document /></el-icon>
            <span>{{ uploadedSize }} / {{ totalSize }}</span>
          </div>
          <div class="info-item" v-if="uploading">
            <el-icon color="#909399"><RefreshLeft /></el-icon>
            <span>并发上传: {{ concurrentUploads }} 个分片</span>
          </div>
        </div>
      </div>

      <!-- 分片详情折叠面板 -->
      <el-collapse class="chunks-collapse" v-if="chunks.length > 0">
        <el-collapse-item title="分片上传详情" name="chunks">
          <div class="chunks-stats">
            <div class="stat-item">
              <span class="stat-label">总分片数</span>
              <span class="stat-value">{{ chunks.length }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">已完成</span>
              <span class="stat-value success">{{ uploadedChunks.size }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">失败</span>
              <span class="stat-value error">{{ errorChunks.length }}</span>
            </div>
            <div class="stat-item">
              <span class="stat-label">分片大小</span>
              <span class="stat-value">{{ formatSize(chunkSize) }}</span>
            </div>
          </div>

          <div class="chunks-grid">
            <div
                v-for="(chunk, index) in chunks"
                :key="index"
                class="chunk-item"
                :class="chunk.status"
                :title="`分片 ${index + 1} (${formatSize(chunk.size)})`"
            >
              <span class="chunk-index">{{ index + 1 }}</span>
              <el-tooltip
                  v-if="chunk.status === 'error'"
                  content="点击重试此分片"
                  effect="dark"
              >
                <el-icon class="chunk-icon" @click="retryChunk(chunk)"><Refresh /></el-icon>
              </el-tooltip>
              <el-icon class="chunk-icon" v-else-if="chunk.status === 'success'"><Check /></el-icon>
              <el-icon class="chunk-icon" v-else-if="chunk.status === 'uploading'"><Loading /></el-icon>
              <el-icon class="chunk-icon" v-else><IconCircle /></el-icon>
            </div>
          </div>
        </el-collapse-item>
      </el-collapse>

      <!-- 上传结果 -->
      <div
          class="upload-result"
          v-if="uploadResult"
          :class="uploadResult.success ? 'result-success' : 'result-error'"
      >
        <el-icon class="result-icon" v-if="uploadResult.success"><IconCheckCircle /></el-icon>
        <el-icon class="result-icon" v-else><IconCloseCircle /></el-icon>

        <div class="result-content">
          <h3 class="result-title">{{ uploadResult.success ? '上传成功' : '上传失败' }}</h3>
          <p class="result-message" v-if="uploadResult.success">
            文件已成功上传至服务器
          </p>
          <p class="result-message" v-else>
            {{ uploadResult.message || '上传过程中发生错误，请重试' }}
          </p>

          <div class="result-actions" v-if="uploadResult.success">
            <p class="file-url">
              <span>文件ID:</span>
              <el-tag type="info" class="file-id">{{ uploadResult.fileId }}</el-tag>
            </p>
            <el-button
                type="text"
                class="copy-btn"
                @click="copyFileId(uploadResult.fileId)"
            >
              <el-icon>
                <IconCopy />
              </el-icon> 复制文件ID
            </el-button>
          </div>
        </div>
      </div>
    </el-card>

    <!-- 上传帮助提示 -->
    <div class="upload-tips">
      <el-descriptions :column="1" border>
        <el-descriptions-item label="断点续传">
          刷新页面或重新上传相同文件可自动续传未完成部分
        </el-descriptions-item>
        <el-descriptions-item label="上传限制">
          单个视频文件不超过10GB，支持后台上传（关闭页面会中断）
        </el-descriptions-item>
        <el-descriptions-item label="格式支持">
          MP4、MOV、AVI、FLV、WMV、MKV（推荐使用MP4格式获得最佳兼容性）
        </el-descriptions-item>
      </el-descriptions>
    </div>
  </div>
</template>

<script setup>
import { ref, computed } from 'vue';
import axios from 'axios';
import {
  UploadFilled, VideoCamera, Delete,
   Loading, Check, Refresh,
    RefreshLeft, Clock, Document
} from '@element-plus/icons-vue';
import { chunkUpload} from "@/util";
import {reqCheckFile, reqCheckChunkFile,reqFileChunkMerge} from "@/api/file";

let props = defineProps({
 sceneCode:{
    type: String,
    required: true
  },
  chunkScene:{
    type: String,
    required: true
  }
});
// 配置参数
const chunkSize = ref(5 * 1024 * 1024); // 5MB 每片
const concurrentUploads = ref(3); // 并发上传数量
const maxRetry = ref(2); // 最大重试次数

// 状态管理
const emits = defineEmits(['uploadSuccess', 'uploadError']);
const selectedFile = ref(null);
const fileInfo = ref({});
const chunks = ref([]);
const uploading = ref(false);
const progress = ref(0);
const uploadStatus = ref('等待文件选择...');
const uploadResult = ref(null);
const fileMD5 = ref('');
const uploadedChunks = ref(new Set());
const totalUploadedBytes = ref(0);
const isDragging = ref(false);
const abortController = ref(null);

// 计算属性
const formatSize = (bytes) => {
  if (bytes === 0) return '0 Bytes';
  const k = 1024;
  const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
const totalSize = computed(() => formatSize(fileInfo.value.size || 0));
const uploadedSize = computed(() => formatSize(totalUploadedBytes.value));
const progressStatus = computed(() => {
  if (uploadResult) return uploadResult ? 'success' : 'exception';
  return uploading.value ? 'processing' : '';
});
const errorChunks = computed(() => {
  return chunks.value.filter(chunk => chunk.status === 'error');
});

// 拖放处理
const handleDrop = (e) => {
  isDragging.value = false;
  if (e.dataTransfer.files.length) {
    const file = e.dataTransfer.files[0];
    if (file.type.startsWith('video/')) {
      handleFile(file);
    } else {
      ElMessage.warning('请选择视频文件');
    }
  }
};
// 文件选择处理
const handleFileSelect = (e) => {
  if (e.target.files.length) {
    handleFile(e.target.files[0]);
  }
};
const handleFile = (file) => {
  // 重置状态
  resetUpload();
  selectedFile.value = file;
  fileInfo.value = {
    name: file.name,
    size: file.size,
    type: file.type,
    lastModified: file.lastModified
  };
  uploadStatus.value = '计算文件MD5...';
  calculateFileMD5(file);
};

// 重置上传
const resetUpload = () => {
  selectedFile.value = null;
  fileInfo.value = {};
  chunks.value = [];
  fileMD5.value = '';
  uploadedChunks.value.clear();
  totalUploadedBytes.value = 0;
  progress.value = 0;
  uploadStatus.value = '等待文件选择...';
  uploadResult.value = null;
  uploading.value = false;
  if (abortController.value) {
    abortController.value.abort();
    abortController.value = null;
  }
};

// 计算文件MD5
const calculateFileMD5 = async (file) => {
  try {
    const spark = new (await import('spark-md5')).default.ArrayBuffer();
    const chunkSize = 2 * 1024 * 1024; // 2MB/块
    const chunksCount = Math.ceil(file.size / chunkSize);
    let currentChunk = 0;
    const fileReader = new FileReader();
    const loadNext = () => {
      const start = currentChunk * chunkSize;
      const end = Math.min(start + chunkSize, file.size);
      fileReader.readAsArrayBuffer(file.slice(start, end));
    };
    fileReader.onload = (e) => {
      spark.append(e.target.result);
      currentChunk++;
      progress.value = (currentChunk / chunksCount) * 10;
      uploadStatus.value = `计算MD5: ${Math.round(progress.value)}%`;
      if (currentChunk < chunksCount) {
        loadNext();
      } else {
        fileMD5.value = spark.end();
        uploadStatus.value = 'MD5计算完成，检查文件状态...';
        checkFileExists();
      }
    };
    fileReader.onerror = () => {
      uploadStatus.value = 'MD5计算失败';
      ElMessage.error('文件读取错误，请重试');
    };

    loadNext();
  } catch (error) {
    uploadStatus.value = 'MD5计算失败';
    ElMessage.error(`MD5计算错误: ${error.message}`);
  }
};

// 检查文件是否已存在
const checkFileExists = async () => {
  try {
    const response = await reqCheckFile(fileMD5.value);
    if (response.data) {
      // 文件已存在
      uploadResult.value = {
        success: true,
        fileId: fileMD5.value,
        message: '文件已存在'
      };
      uploadStatus.value = '文件已存在，无需重复上传';
      progress.value = 100;
      return;
    }
    // 创建分片
    createChunks();
  } catch (error) {
    uploadStatus.value = '检查文件状态失败，继续上传';
    createChunks();
  }
};

// 创建分片
const createChunks = () => {
  const fileSize = selectedFile.value.size;
  const chunksCount = Math.ceil(fileSize / chunkSize.value);
  chunks.value = [];
  for (let i = 0; i < chunksCount; i++) {
    const start = i * chunkSize.value;
    const end = Math.min(start + chunkSize.value, fileSize);
    chunks.value.push({
      index: i,
      start,
      end,
      size: end - start,
      status: 'pending',
      retry: 0,
      file: selectedFile.value.slice(start, end)
    });
  }
  uploadStatus.value = '检查已上传分片...';
  checkExistingChunks();
};

// 检查已上传分片
const checkExistingChunks = async () => {
  try {
    const totalChunks = chunks.value.length;
    if (totalChunks === 0) return;
    // 并发检查分片
    const results = await Promise.all(
        chunks.value.map(chunk =>
            reqCheckChunkFile(props.chunkScene,fileMD5.value, chunk.index)
                .then(res => ({ chunk, exists: res.data }))
                .catch(() => ({ chunk, exists: false }))
        )
    );
    results.forEach(({ chunk, exists }) => {
      if (exists) {
        chunk.status = 'success';
        uploadedChunks.value.add(chunk.index);
        totalUploadedBytes.value += chunk.size;
      }
    });
    // 更新进度
    progress.value = Math.min(
        (totalUploadedBytes.value / fileInfo.value.size) * 90 + 10,
        100
    );
    // 所有分片已上传
    if (uploadedChunks.value.size === totalChunks) {
      uploadStatus.value = '所有分片已上传，开始合并...';
      mergeChunks();
    } else {
      uploadStatus.value = `可继续上传 (已完成 ${uploadedChunks.value.size}/${totalChunks} 个分片)`;
    }
  } catch (error) {
    console.log("错误信息",error)
    uploadStatus.value = '分片检查失败，继续上传'+error;
  }
};
// 开始上传
const startUpload = async () => {
  if (!selectedFile.value || uploading.value) return;
  uploading.value = true;
  abortController.value = new AbortController();
  uploadStatus.value = '开始上传分片...';
  // 过滤未上传的分片
  const pendingChunks = chunks.value.filter(chunk =>
      !uploadedChunks.value.has(chunk.index) && chunk.status !== 'error'
  );
  if (pendingChunks.length === 0) {
    uploadStatus.value = '所有分片已上传，开始合并...';
    await mergeChunks();
    return;
  }
  // 并发上传分片
  await uploadChunksInBatches(pendingChunks);
};

// 分批并发上传
const uploadChunksInBatches = async (pendingChunks) => {
  const batchSize = concurrentUploads.value;
  for (let i = 0; i < pendingChunks.length; i += batchSize) {
    if (!uploading.value) break; // 已取消上传

    const batch = pendingChunks.slice(i, i + batchSize);
    await Promise.all(batch.map(chunk => uploadChunk(chunk)));
    uploadStatus.value = `上传中 (${uploadedChunks.value.size}/${chunks.value.length})`;
  }
  // 检查是否所有分片都已上传
  if (uploadedChunks.value.size === chunks.value.length && uploading.value) {
    uploadStatus.value = '所有分片上传完成，开始合并...';
    await mergeChunks();
  } else if (uploading.value) {
    uploadStatus.value = `上传暂停 (已完成 ${uploadedChunks.value.size}/${chunks.value.length})`;
    uploading.value = false;
  }
};

// 上传单个分片
const uploadChunk = async (chunk) => {
  if (uploadedChunks.value.has(chunk.index) || !uploading.value) return;
  chunk.status = 'uploading';
  try {
    const formData = new FormData();
    formData.append('file', chunk.file);
    formData.append('md5', fileMD5.value);
    formData.append('chunk', chunk.index);
    formData.append('sceneCode',props.chunkScene);
    const res = await axios.post(
        chunkUpload,
        formData,
        {
          headers: { 'Content-Type': 'multipart/form-data' },
          signal: abortController.value.signal
        }
    );
    if (res.data.data) {
      chunk.status = 'success';
      uploadedChunks.value.add(chunk.index);
      totalUploadedBytes.value += chunk.size;
      // 更新进度
      progress.value = Math.min(
          (totalUploadedBytes.value / fileInfo.value.size) * 90 + 10,
          100
      );
      return true;
    } else {
      throw new Error(res.data.msg || '分片上传失败');
    }
  } catch (error) {
    if (error.name === 'AbortError') {
      chunk.status = 'cancelled';
      return false;
    }
    chunk.retry++;
    if (chunk.retry >= maxRetry.value) {
      chunk.status = 'error';
      uploadStatus.value = `分片 ${chunk.index + 1} 上传失败`;
      return false;
    }
    // 重试
    await new Promise(resolve => setTimeout(resolve, 1000 * chunk.retry));
    return uploadChunk(chunk);
  }
};

// 重试单个分片
const retryChunk = (chunk) => {
  if (chunk.status === 'error') {
    chunk.status = 'pending';
    chunk.retry = 0;
    if (!uploading.value) {
      uploading.value = true;
      uploadChunksInBatches([chunk]);
    } else {
      uploadChunk(chunk);
    }
  }
};

// 合并分片
const mergeChunks = async () => {
  try {
    const response = await reqFileChunkMerge(
        {
          md5: fileMD5.value,
          fileName: selectedFile.value.name,
          chunkTotal: chunks.value.length,
          mergeScene: props.sceneCode,
          chunkScene: props.chunkScene
        }
    );
    if (response.code === 200) {
      uploadStatus.value = '上传完成';
      progress.value = 100;
      uploadResult.value = {
        success: true,
        fileId: response.data.id,
        url: response.data.visitPath
      };
      emits('uploadSuccess', uploadResult.value.url);
    } else {
      throw new Error(response.msg || '合并分片失败');
    }
  } catch (error) {
    if (error.name !== 'AbortError') {
      uploadStatus.value = `合并失败: ${error.message}`;
      uploadResult.value = {
        success: false,
        message: error.message
      };
      emits('uploadError', error.message);
    }
  } finally {
    uploading.value = false;
  }
};

// 取消上传
const cancelUpload = () => {
  if (abortController.value) {
    abortController.value.abort();
  }
  uploading.value = false;
  uploadStatus.value = '已取消上传';
  // 更新分片状态
  chunks.value.forEach(chunk => {
    if (chunk.status === 'uploading') {
      chunk.status = 'pending';
    }
  });
};

// 复制文件ID
const copyFileId = (fileId) => {
  navigator.clipboard.writeText(fileId).then(() => {
    ElMessage.success('文件ID已复制');
  });
};
</script>

<style scoped>
.upload-container {
  max-width: 900px;
  margin: 30px auto;
  padding: 0 20px;
}

.upload-card {
  border-radius: 12px;
  overflow: hidden;
  border: none;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
  padding: 24px;
}

.card-title {
  font-size: 20px;
  font-weight: 600;
  margin: 0;
  color: #1f2329;
}

.card-subtitle {
  font-size: 14px;
  color: #6b7280;
  margin: 5px 0 0;
}

/* 拖放区域 */
.drop-area {
  border: 2px dashed #c9cdD4;
  border-radius: 8px;
  padding: 40px 20px;
  text-align: center;
  transition: all 0.3s ease;
  margin-bottom: 20px;
  background-color: #fafafa;
}
.drop-area:hover{
  border-color: #409EFF;
  background-color: #f0f7ff;
}
.drop-active {
  border-color: #409EFF;
  background-color: #f0f7ff;
}

.file-input {
  display: none;
}

.drop-content {
  max-width: 500px;
  margin: 0 auto;
}

.upload-icon {
  font-size: 48px;
  color: #409EFF;
  margin-bottom: 16px;
}

.drop-title {
  font-size: 18px;
  color: #333;
  margin: 0 0 12px;
}

.drop-desc {
  color: #666;
  margin: 12px 0;
}

.browse-btn {
  margin: 8px 0 16px;
}

.format-tip {
  font-size: 12px;
  color: #999;
  margin: 0;
}

/* 文件信息卡片 */
.file-info-card {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 16px;
  margin-bottom: 20px;
  background-color: #f9fafb;
}

.file-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.file-icon {
  font-size: 24px;
  color: #409EFF;
  margin-right: 12px;
}

.file-meta {
  flex: 1;
}

.file-name {
  font-size: 16px;
  font-weight: 500;
  margin: 0 0 4px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.file-size {
  font-size: 13px;
  color: #666;
  margin: 0;
}

.remove-btn {
  padding: 4px;
}

/* 控制按钮 */
.control-buttons {
  display: flex;
  gap: 12px;
  margin-bottom: 24px;
}

.main-btn {
  padding: 10px 24px;
}

.cancel-btn {
  padding: 10px 24px;
}

.loading-icon {
  animation: spin 2s infinite linear;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* 进度区域 */
.progress-section {
  margin-bottom: 24px;
}

.progress-container {
  margin-bottom: 12px;
}

.progress-label {
  display: flex;
  justify-content: space-between;
  margin-bottom: 8px;
}

.progress-percent {
  font-weight: 500;
  color: #409EFF;
}

.progress-bar {
  width: 100%;
}

.progress-info {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  font-size: 13px;
}

.info-item {
  display: flex;
  align-items: center;
  gap: 6px;
  color: #666;
}

/* 分片详情 */
.chunks-collapse {
  margin-bottom: 24px;
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  overflow: hidden;
}

.chunks-stats {
  display: flex;
  flex-wrap: wrap;
  gap: 20px;
  margin-bottom: 16px;
  padding-bottom: 16px;
  border-bottom: 1px dashed #e5e7eb;
}

.stat-item {
  display: flex;
  flex-direction: column;
}

.stat-label {
  font-size: 12px;
  color: #999;
}

.stat-value {
  font-size: 14px;
  font-weight: 500;
}

.success {
  color: #67C23A;
}

.error {
  color: #f56c6c;
}

.chunks-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(40px, 1fr));
  gap: 8px;
}

.chunk-item {
  width: 40px;
  height: 40px;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
  position: relative;
}

.chunk-item.pending {
  background-color: #f5f5f5;
  color: #999;
  border: 1px solid #e5e5e5;
}

.chunk-item.uploading {
  background-color: #e6f7ff;
  color: #409EFF;
  border: 1px solid #91d5ff;
}

.chunk-item.success {
  background-color: #f0f9eb;
  color: #67C23A;
  border: 1px solid #b7eb8f;
}

.chunk-item.error {
  background-color: #fff2f0;
  color: #f56c6c;
  border: 1px solid #ffa39e;
  cursor: pointer;
}

.chunk-index {
  position: absolute;
  top: 2px;
  left: 2px;
  font-size: 8px;
}

.chunk-icon {
  font-size: 16px;
}

/* 上传结果 */
.upload-result {
  border-radius: 8px;
  padding: 20px;
  margin-top: 16px;
  display: flex;
  align-items: flex-start;
  gap: 16px;
}

.result-success {
  background-color: #f0f9eb;
  border: 1px solid #e1f3d8;
}

.result-error {
  background-color: #fff2f0;
  border: 1px solid #ffe3e3;
}

.result-icon {
  font-size: 24px;
  margin-top: 2px;
}

.result-success .result-icon {
  color: #67C23A;
}

.result-error .result-icon {
  color: #f56c6c;
}

.result-content {
  flex: 1;
}

.result-title {
  font-size: 18px;
  margin: 0 0 8px;
  color: #333;
}

.result-message {
  color: #666;
  margin: 0 0 16px;
  line-height: 1.5;
}

.file-url {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  margin: 0 0 12px;
}

.file-id {
  max-width: 300px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.copy-btn {
  padding: 4px 8px;
}

/* 上传提示 */
.upload-tips {
  margin-top: 20px;
  font-size: 14px;
}

/* 响应式适配 */
@media (max-width: 768px) {
  .upload-card {
    padding: 16px;
  }

  .drop-area {
    padding: 30px 10px;
  }

  .upload-icon {
    font-size: 36px;
  }

  .drop-title {
    font-size: 16px;
  }

  .control-buttons {
    flex-direction: column;
  }

  .main-btn, .cancel-btn {
    width: 100%;
  }

  .chunks-grid {
    grid-template-columns: repeat(auto-fill, minmax(30px, 1fr));
  }

  .chunk-item {
    width: 30px;
    height: 30px;
  }
}
</style>
